package
{
	import flash.display.*;
	import flash.events.Event;
	import flash.geom.Point;
	
	/** This class creates an object that is related to but separate from DrawSegment and DrawPoint in that it contains links to
	 * three points and two segments.  Optionally, we can draw an arc to indicate the position of this internal angle */
	public class DrawAngle extends MovieClip
	{
		// constants
		public static const DEGREE_DISCRIM_THRESH:Number = .1; // The minimum difference (in degrees) that we adjust a line to match the slope of another line
		public static const DIAMETER:int = 40;
		/** private objects */
		private var _point1:DrawPoint,_point2:DrawPoint, _pointC:DrawPoint; //needs all three
		private var _segment1:DrawSegment, _segment2:DrawSegment; // needs both
		private var _congruentPartners:Array = null;
		private var congruentMarking:Shape = null;
		/** private vars */
		private var _radius:Number=0;
		private var _congruentCount:uint = 0; // how many markings
		private var _right:Boolean = false; // been validated right?
		
		public function get congruentCount():uint{return _congruentCount;}
		public function get congruentPartners():Array{return _congruentPartners;}
		public function get right():Boolean{return _right;}
		public function get segment1():DrawSegment{return _segment1}
		public function get segment2():DrawSegment{return _segment2}
		public function get point1():DrawPoint{return _point1}
		public function get point2():DrawPoint{return _point2}
		public function get pointC():DrawPoint{return _pointC}
		public function get endpoint1():Point{return _point1.point}
		public function get endpoint2():Point{return _point2.point}
		public function get centerpoint():Point{return _pointC.point}
		public function get bisectorAngle():Number{return MathUtilities.calcAngleBisector(_segment1.angleFrom(_pointC), _segment2.angleFrom(_pointC));}
		public function get bisectorRotation():Number{return MathUtilities.radToDeg(MathUtilities.calcAngleBisector(_segment1.angleFrom(_pointC), _segment2.angleFrom(_pointC)));}
		
		public function get degrees():Number {return MathUtilities.calcAcuteRotationBetween(_point1.point, _point2.point, _pointC.point);}
		public function get radians():Number {return MathUtilities.calcAcuteAngleBetween(_point1.point, _point2.point, _pointC.point);}
		
		/** Can be constructed with either segments (2) or points (3).  Order of points is important */ 
		public function DrawAngle(_segment1:DrawSegment=null, _segment2:DrawSegment=null, _point1:DrawPoint=null, _point2:DrawPoint=null, _pointC:DrawPoint=null)
		{
			var s1:DrawSegment, s2:DrawSegment;
			var p:DrawPoint;
			var componentsFound:Boolean = false;
			this.mouseEnabled = false;
			_congruentPartners = new Array();
			if (_segment1 != null && _segment2 != null)
			{
				// make sure these segments have a point in common
				if (_segment1.point1 == _segment2.point1 || _segment1.point1 == _segment2.point2 || _segment1.point2 == _segment2.point1 || _segment1.point2 == _segment2.point2)
				{
					componentsFound = true;
					this._segment1 =_segment1;
					this._segment2 =_segment2;
					if (_segment1.point1 == _segment2.point1)
					{
						this._pointC = _segment1.point1;
						this._point1 = _segment1.point2;
						this._point2 = _segment2.point2;
					} else if (_segment1.point1 == _segment2.point2)
					{
						this._pointC = _segment1.point1;
						this._point1 = _segment1.point2;
						this._point2 = _segment2.point1;
					} else if (_segment1.point2 == _segment2.point1)
					{
						this._pointC = _segment1.point2;
						this._point1 = _segment1.point1;
						this._point2 = _segment2.point2;
					} else if (_segment1.point2 == _segment2.point2)
					{
						this._pointC = _segment1.point2;
						this._point1 = _segment1.point1;
						this._point2 = _segment2.point1;
					} else {
						trace("huh, in DrawAngle constructor this shouldn't happen");
					}
				} else 
				{
					trace("in new angle, problem matching points", _segment1.endpoint1, _segment1.endpoint2, _segment2.endpoint1, _segment2.endpoint2, _segment1.endpoint2==_segment2.endpoint2, _segment1.point2==_segment2.point2);
				}
			} else if (_point1 != null && _point2 != null && _pointC != null)
			{
				var segments:Array = _pointC.drawSegments;
				for (var i:int=0; i < segments.length-1; i++)
				{	
					s1 = segments[i];
					// does the current segment contain both pointC and either point1 or point2
					if ( (s1.point1 == _point1 && s1.point2 == _pointC) || (s1.point1 == _point1 && s1.point2 == _pointC) || (s1.point1 == _point2 && s1.point2 == _pointC) || (s1.point1 == _point2 && s1.point2 == _pointC))
					{
						// look through the rest of the array for the other segment
						for (var j:int=i+1; j < segments.length; j++)
						{
							// Check to make sure everything adds up
							if ( (s1.point1 == _point1 && s1.point2 == _pointC && s2.point1 == _point2 && s2.point2 == _pointC) || (s1.point1 == _point1 && s1.point2 == _pointC && s2.point1 == _point2 && s2.point2 == _pointC) || (s1.point1 == _point2 && s1.point2 == _pointC && s2.point1 == _point1 && s2.point2 == _pointC) || (s1.point1 == _point2 && s1.point2 == _pointC && s2.point1 == _point1 && s2.point2 == _pointC) || (s1.point1 == _point1 && s1.point2 == _pointC && s2.point1 == _pointC && s2.point2 == _point2) || (s1.point1 == _point1 && s1.point2 == _pointC && s2.point1 == _pointC && s2.point2 == _point2) || (s1.point1 == _point2 && s1.point2 == _pointC && s2.point1 == _pointC && s2.point2 == _point1) || (s1.point1 == _point2 && s1.point2 == _pointC && s2.point1 == _pointC && s2.point2 == _point1))
							{
								componentsFound = true;
								this._point1 = _point1;
								this._point2 = _point2;
								this._pointC = _pointC;
								if (s1.point1 == _point1 || s1.point2 == _point1)
								{
									this._segment1 = s1;
									this._segment2 = s2;
								} else
								{
									this._segment2 = s1;
									this._segment1 = s2;
								}
							}
						}
					}
				}
			}
			
			// Did we find the right components?
			if (componentsFound)
			{
				this.graphics.beginFill(0xFFFFFF,0);
				this.graphics.drawEllipse(-DIAMETER/2,-DIAMETER/2,DIAMETER,DIAMETER);
				this.graphics.endFill();
				// add references in points
				this._point1.addAngle(this);
				this._point2.addAngle(this);
				this._pointC.addAngle(this);
				
				//center this at pointC
				this.x = this._pointC.x;
				this.y = this._pointC.y;
				this.dispatchEvent(new Event(Event.COMPLETE));
			} else
			{
				trace("Incorrect components for angle");
				this.dispatchEvent(new Event(Event.CANCEL));
			}
		}
		
		public function containsDrawPoint(drawpoint:DrawPoint):Boolean
		{
			if (drawpoint == point1 || drawpoint == point2 || drawpoint == pointC)
			{
				return true;
			} else
			{
				return false;
			}
		}
		public function containsPoint(point:Point):Boolean
		{
			if (point.x == point1.x && point.y == point1.y || point.x == point2.x && point.y == point2.y || point.x == pointC.x && point.y == pointC.y)
			{
				return true;
			} else
			{
				return false;
			}
		}
		public function containsDrawSegment(drawsegment:DrawSegment):Boolean
		{
			if (this._segment1 == drawsegment || this._segment2 == drawsegment)
			{
				return true;
			} else
			{
				return false;
			}
		}
		public function redraw(dp:DrawPoint = null):void
		{
			// we won't bother if the given drawpoint is not included
			if (dp == null || containsDrawPoint(dp))
			{					
				this.x = this._pointC.x;
				this.y = this._pointC.y;
				this.congruentCount = congruentCount;
			}
		}
		/** draws an arc with the given radius between the two segments */
		/*
		public function drawArc(radius:Number=-999, d:DisplayObject=null):void
		{
			if (d == null) d = this;
			d.graphics.clear();
			d.graphics.beginFill(0xFFFFFF,0);
			d.graphics.drawEllipse(-DIAMETER/2,-DIAMETER/2,DIAMETER,DIAMETER);
			d.graphics.endFill();
			if (radius != -999) d._radius = radius;
			d.x = _pointC.x;
			d.y = _pointC.y;
			// Draw directly on this object
			if (_radius > 0)
			{
				//trace("degress:", degrees,_segment1.rotationFrom(_pointC), _segment2.rotationFrom(_pointC));
				MathUtilities.drawArc(this.graphics,2, 0xFF0000, 0, 0, _segment2.angleFrom(_pointC), _segment1.angleFrom(_pointC),  _radius);
			}
		}
		
		public function clearArc():void
		{
			_radius = 0;
			this.graphics.clear();
		}
		*/
		public function adjustToDegrees(deg:Number, percent:Number=1, tangle:DrawAngle=null):Boolean
		{
			if (Math.abs(deg-this.degrees) > DEGREE_DISCRIM_THRESH)
			{
				var d:Number = percent*(deg-this.degrees);
				if (d!=0) adjustDegrees(d, tangle);
				return true;
			}else 
			{
				return false;
			}		
		}
		public function adjustToRadians(ang:Number, percent:Number=1,  tangle:DrawAngle=null):Boolean
		{
			var d:Number;
			//trace(MathUtilities.radToDeg(ang),MathUtilities.radToDeg(this.radians), MathUtilities.radToDeg(Math.abs(ang-this.radians)), MathUtilities.radToDeg(DEGREE_DISCRIM_THRESH/180*Math.PI));
			if (Math.abs(ang-this.radians) > DEGREE_DISCRIM_THRESH/180*Math.PI)
			{
				d = percent*(ang-this.radians);
				if (d!=0) adjustRadians(d, tangle);
				return true;
			}else 
			{
				d = (ang-this.radians);
				if (d!=0) adjustRadians(d, tangle);
				return false;
			}		
		}
		/**** Adjusting points and segments */
		public function adjustDegrees (deg:Number,  tangle:DrawAngle=null):void
		{
			adjustRadians(MathUtilities.degToRad(deg), tangle);
		}
		/**** Adjusting points and segments */
		public function adjustRadians (rad:Number, tangle:DrawAngle=null):void
		{
			if (tangle != null)
			{
				//// For each of the endpoints of this angle we want to figure out on which segment we will dilate, or not
				
				////////////////////  endpoint 1 ////////////////////////////
				//// There are thee main possibilities in the relationship between this angle and the target angle (tangle)
				// 1) These angles are adjacent, we don't want to effect the segment coming out of the target angle, just the segment on the other side
				// 2) These are separated by two segments (opposite in quad), we want to adjust both sides of the target angle without changing the angle itself
				// 3) These angles have more than two degrees of separation, so doesn't matter how we adjust this angle
				    //1) Is point1 and the target point adjacent, if so we will not be adjusting a segment on this side
				var adjangle1:DrawAngle;
				var adjsegment1:DrawSegment;
				if (this.point1 == tangle.pointC)
				{
					adjangle1 = null;
					adjsegment1 = null;
				}  //2) Are these angles separated by two degrees? If so we will make sure to adjust the correct segment
				else if (this.point1 == tangle.point1 || this.point1 == tangle.point2)
				{
					if (this.point1 == tangle.point1)
					{
						adjsegment1 = tangle.segment1;
						adjangle1 = this.angleAtEndPoint(adjsegment1);
					} else if (this.point1 == tangle.point2)
					{
						adjsegment1 = tangle.segment2;
						adjangle1 = this.angleAtEndPoint(adjsegment1);
					} else
					{
						trace("In DrawAngle, shouldn't happen");
					}
				}  //3) Are these angles separated by more than two degres, then the segment we dilate does not matter (but we'll pick a useful one) 
				else
				{
					// get a segment at point1
					adjangle1 = this.point1.getRightestAngle(segment1);
					//adjsegment1 = adjangle1.otherDrawSegment(segment1);
					adjsegment1 = null;
				}
				
				////////////////////  endpoint 2 ////////////////////////////
				//// There are thee main possibilities in the relationship between this angle and the target angle (tangle)
				// 1) These angles are adjacent, we don't want to effect the segment coming out of the target angle, just the segment on the other side
				// 2) These are separated by two segments (opposite in quad), we want to adjust both sides of the target angle without changing the angle itself
				// 3) These angles have more than two degrees of separation, so doesn't matter how we adjust this angle
				//1) Is point1 and the target point adjacent, if so we will not be adjusting a segment on this side
				var adjangle2:DrawAngle;
				var adjsegment2:DrawSegment;
				if (this.point2 == tangle.pointC)
				{
					adjangle2 = null;
					adjsegment2 = null;
				}  //2) Are these angles separated by two degrees? If so we will make sure to adjust the correct segment
				else if (this.point2 == tangle.point1 || this.point2 == tangle.point2)
				{
					if (this.point2 == tangle.point1)
					{
						adjsegment2 = tangle.segment1;
						adjangle2 = this.angleAtEndPoint(adjsegment2);
					} else if (this.point2 == tangle.point2)
					{
						adjsegment2 = tangle.segment2;
						adjangle2 = this.angleAtEndPoint(adjsegment2);
					} else
					{
						trace("In DrawAngle, shouldn't happen");
					}
				}  //3) Are these angles separated by more than two degres, then the segment we dilate does not matter (but we'll pick a useful one) 
				else
				{
					// get a segment at point2
					adjangle2 = this.point2.getRightestAngle(segment2);
					//adjsegment2 = adjangle2.otherDrawSegment(segment2);
					adjsegment2 = null;
				}
				
				// now that we know which segments we are adjusting allocate an amount to each
				var rad1:Number, rad2:Number;
				if (adjangle1 != null && adjangle2 != null)
				{
					rad1 = rad/2; rad2 = rad/2;	
				} else if (adjangle1 != null)
				{
					rad1 = rad; rad2 = 0;
				} else if (adjangle2 != null)
				{
					rad2 = rad; rad1 = 0;
				} else 
				{
					return;
				}
				
			
				/// Adjust segment 1 according to the given angles
				/// A triangle is formed between this (center) point, the adjacent point that is shifting, and that point's eventual position
				/// we can get two angles and a side and use Law of Sines
				/// Get angle at current adjacent Point
				if (rad1 != 0 && adjsegment1 != null)
				{
					var C1:Number = adjangle1.radians;				
					/// if our change is positive, need to find supplement of angle because triangle is "external"
					if (rad1 > 0) C1 = Math.PI - C1;
					/// Complete the triangle
					var B1:Number = Math.PI - C1 - Math.abs(rad1);
					/// Law of Sines
					var dx1:Number = MathUtilities.lawOfSines(segment1.length, B1, 0, Math.abs(rad1));
					if (rad1 < 0) dx1 = -dx1;
					//trace("segment1: adjusting angle", rad1, adjangle1.degrees, "adjusting side", adjsegment1.length, "amount", dx1);
					adjsegment1.dilate(dx1, this.point1);
				}
				/// Adjust segment 2 according to the given angles
				/// A triangle is formed between this (center) point, the adjacent point that is shifting, and that point's eventual position
				/// we can get two angles and a side and use Law of Sines
				/// Get angle at current adjacent Point
				if (rad2 != 0 && adjsegment2 != null)
					{var C2:Number = adjangle1.radians;				
					/// if our change is positive, need to find supplement of angle because triangle is "external"
					if (rad2 > 0) C2 = Math.PI - C2;
					/// Complete the triangle
					var B2:Number = Math.PI - C2 - Math.abs(rad2);
					/// Law of Sines
					var dx2:Number = MathUtilities.lawOfSines(segment2.length, B2, 0, Math.abs(rad2));
					if (rad2 < 0) dx2 = -dx2;
					//trace("segment2: adjusting angle", rad2, adjangle2.degrees, "adjusting side", adjsegment2.length, "amount", dx2);
					adjsegment2.dilate(dx2, this.point2);
				}
				
				/// We are just rotating the segment because it doesn't need to be constrained to a specific line
				if (rad1 != 0 && adjsegment1 == null)
				{
					var dir1:int = MathUtilities.calcAcuteDirectionBetween(this._point1.point, this._point2.point, this._pointC.point);
					if(dir1 != 0) this._segment1.rotateRadians(dir1*rad1,this._pointC.point);
				}
				if (rad2 != 0 && adjsegment2 == null)
				{
					var dir2:int = MathUtilities.calcAcuteDirectionBetween(this._point2.point, this._point1.point, this._pointC.point);
					if (dir2 != 0) this._segment2.rotateRadians(dir2*rad2,this._pointC.point);
				}
				
			} // 4) We are not trying to match a target angle, just rotate these segments without regards to other angles			 
			else
			{
				// we need direction in order to adjust correctly
				//trace("acute direction", MathUtilities.calcAcuteDirectionBetween(this._point1.point, this._point2.point, this._pointC.point));
				var dir:int = MathUtilities.calcAcuteDirectionBetween(this._point1.point, this._point2.point, this._pointC.point);
				if (dir != 0)
				{
					this._segment1.rotateRadians(dir*rad/2,this._pointC.point);
					this._segment2.rotateRadians(dir*-rad/2,this._pointC.point);
				}
			}
			
			point1.redrawSegments();
			point2.redrawSegments();
			point1.redrawAngles();
			point2.redrawAngles();
			pointC.redrawAngles();
		}
		
		///////*** Get stuff /////////////////////
		/** Get the other segment at this angle */
		public function otherDrawSegment(s:DrawSegment):DrawSegment
		{
			if (s == this.segment1)
			{
				return this.segment2;
			} else if (s == this.segment2)
			{
				return this.segment1;
			} else
			{
				return null;
			}
		}
		/** Looks for this segment at each of the points (including center) and returns angle that is included, can be this one*/
		public function angleAtEndPoint(s:DrawSegment):DrawAngle
		{
			if (s == this.segment1)
			{
				return this;
			} else if (s == this.segment2)
			{
				return this;
			} else 
			{
				var a:DrawAngle
				// go through end point to see if the segment is attached
				for each (a in point1.drawAngles)
				{
					// found segment in the next angle?
					if (a.segment1 == s && (a.segment2 == this.segment1 || a.segment2 == this.segment1) || a.segment2 == s && (a.segment1 == this.segment1 || a.segment1 == this.segment2))
					{
						// return angle 	
						return a;
					}
				}
				for each (a in point2.drawAngles)
				{
					// found segment in the next angle?
					if (a.segment1 == s && (a.segment2 == this.segment1 || a.segment2 == this.segment1) || a.segment2 == s && (a.segment1 == this.segment1 || a.segment1 == this.segment2))
					{
						// return angle 	
						return a;
					}
				}
				return null;
			}
		}
		
		/** Adds a new partner to this segments list of congruent partners.  If already exists return false, else return true; 
		 * Also, updates the count to reflect the minimum of all partners.
		 * */
		public function addCongruentPartner(da:DrawAngle, count:uint=0):uint
		{
			if (count == 0) count = congruentCount;
			
			var found:Boolean = false;
			for each (var _da:DrawAngle in this.congruentPartners)
			{
				if (_da == da) 
				{
					found = true;
				}
			}
			if (!found)
			{
				congruentPartners.push(da);
				// do converse
				da.addCongruentPartner(this, count);
			}
			return setMinimumCongruentCount(count);
		}
		/** Removes the Draw Segment from this objects collection, does not do converse */
		public function removeCongruentPartner(da:DrawAngle):Boolean
		{
			var i:int = this.congruentPartners.indexOf(da);
			if (i >= 0)
			{
				this.congruentPartners.splice(i,1);
				return true;			
			}
			return false;
		}
		/** Go through all partners and make sure that all congruent counts are the same, and minimized */
		public function setMinimumCongruentCount (count:uint=0):uint
		{
			if (this.congruentPartners.length > 0)
			{
				var minCount:uint;
				if (count == 0)
				{
					minCount = congruentCount;
				} else if(congruentCount == 0)
				{
					minCount = count;
				} else 
				{	
					minCount = Math.min(count, congruentCount);
				}
				for each (var _da:DrawAngle in this.congruentPartners)
				{
					if (_da.congruentCount < minCount && _da.congruentCount > 0) minCount = _da.congruentCount;
					if (_da.right) this.right;
				}
				this.congruentCount = minCount;
				for each (_da in this.congruentPartners){ _da.congruentCount = minCount; _da.right=this.right}
				
				return minCount;
			} else
			{
				this.congruentCount = 0;
				return 0;
			}
		}
		/** Applies congruency markings, one marking for each count. If zero, remove. */
		public function set congruentCount(count:uint):void
		{
			_congruentCount = count;
			if (right)
			{
				this.right = right;
			}
			else if (_congruentCount == 0)
			{
				if (this.congruentMarking != null)
				{
					this.removeChild(congruentMarking);
					congruentMarking = null;
				}
			} else //greater than zero
			{
				if (this.congruentMarking == null)
				{
					congruentMarking = new Shape();
					this.addChild(congruentMarking);
				} else
				{
					congruentMarking.graphics.clear();
				}
				var i:uint
				congruentMarking.graphics.beginFill(0x666666,0);
				congruentMarking.graphics.drawEllipse(-DIAMETER/2,-DIAMETER/2,DIAMETER,DIAMETER);
				congruentMarking.graphics.endFill();
				
				for (i=0; i < congruentCount; i++)
				{
					MathUtilities.drawArc(congruentMarking.graphics,2, 0x0000FF, 0, 0, _segment2.angleFrom(_pointC), _segment1.angleFrom(_pointC), (i+1)/2*DIAMETER);
				}
				
			}
		}
		/** Looks for all congruencies with this segment and checks if they are still valid, use threshold to determine congruency.
		 * Returns true if a change has been made */
		public function checkExistingCongruencies(threshold:Number):Boolean
		{
			var changed:Boolean = false;
			for each (var _da:DrawAngle in this.congruentPartners)
			{
				if (Math.abs(this.degrees - _da.degrees) > threshold)
				{
					this.removeCongruentPartner(_da);
					_da.removeCongruentPartner(this);
					changed = true;
				}
			}
			return changed;
		}
		/** Set the angle marking to a square if true, else look up congruency markings */
		public function set right(b:Boolean):void
		{
			_right = b;
			if (right)
			{
				if (this.congruentMarking == null)
				{
					congruentMarking = new Shape();
					this.addChild(congruentMarking);
				} else
				{
					congruentMarking.graphics.clear();
				}
				var i:uint
				congruentMarking.graphics.beginFill(0x666666,0);
				congruentMarking.graphics.drawEllipse(-DIAMETER/2,-DIAMETER/2,DIAMETER,DIAMETER);
				congruentMarking.graphics.endFill();
				
				congruentMarking.graphics.lineStyle(2, 0x0000FF);
				congruentMarking.graphics.moveTo(DIAMETER/2*Math.sin(_segment2.angleFrom(_pointC)),-DIAMETER/2*Math.cos(_segment2.angleFrom(_pointC)));
				congruentMarking.graphics.lineTo(DIAMETER/2*Math.SQRT2*Math.sin(this.bisectorAngle),-DIAMETER/2*Math.SQRT2*Math.cos(this.bisectorAngle));
				congruentMarking.graphics.lineTo(DIAMETER/2*Math.sin(_segment1.angleFrom(_pointC)),-DIAMETER/2*Math.cos(_segment1.angleFrom(_pointC)));	
		
			}else
			{
				this.congruentCount = congruentCount;
			}
		}	
		/** Looks for all congruencies with this segment and checks if they are still valid, use threshold to determine congruency.
		 * Returns true if a change has been made */
		public function checkExistingRight(threshold:Number):Boolean
		{
			if (right)
			{
				
				if (Math.abs(this.degrees - 90) > threshold) { right = false; return true; }
				else { return false;}
				
			} else
			{
				return false;
			}
		}
	}
}